package com.physics.mps.module.ansys.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.physics.mps.common.config.properties.ApiProperties;
import com.physics.mps.common.exception.BusinessException;
import com.physics.mps.common.util.AssertUtil;
import com.physics.mps.common.util.LoginUtil;
import com.physics.mps.common.util.MinioUtil;
import com.physics.mps.common.util.OkHttpUtil;
import com.physics.mps.entity.dto.SubTaskEntity;
import com.physics.mps.entity.dto.TaskComputeResultEntity;
import com.physics.mps.entity.enums.CodeEnum;
import com.physics.mps.entity.enums.ScriptStatusEnum;
import com.physics.mps.entity.vo.ResponseResult;
import com.physics.mps.entity.vo.StartComputeVo;
import com.physics.mps.entity.vo.api.Result;
import com.physics.mps.entity.vo.api.UploadVo;
import com.physics.mps.mapper.AppModuleMapper;
import com.physics.mps.module.ansys.builder.*;
import com.physics.mps.module.ansys.service.AnsysService;
import com.physics.mps.module.ansys.vo.AnsysResultVo;
import com.physics.mps.module.ansys.vo.AnsysSolveVo;
import com.physics.mps.module.ansys.vo.AnsysTempVo;
import com.physics.mps.module.ansys.vo.PartVo;
import com.physics.mps.module.compute.TaskComputeResultService;
import com.physics.mps.module.task.sub.SubTaskService;
import com.physics.mps.module.util.AnsysJsonResolver;
import com.physics.mps.module.util.PyUtils;
import com.physics.mps.module.util.TaskMonitor;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/**
 * @author mcsoft
 * @since 2023/11/1
 */
@Log4j2
@RequiredArgsConstructor
@Service
public class AnsysServiceImpl implements AnsysService {

    /**
     * spaceclaim前处理软件路径
     */
    @Value("${simulate.ansys.spaceclaim-path}")
    private String spaceclaimPath;

    /**
     * ansys求解器路径
     */
    @Value("${simulate.ansys.ansys-path}")
    private String ansysPath;

    /**
     * ansys脚本存放地址
     */
    @Value("${simulate.ansys.py-path}")
    private String ansysPyPath;

    /**
     * 模型名称存放地址
     */
    @Value("${simulate.ansys.model-name}")
    private String modelnamePath;

    /**
     * ansys工作目录
     */
    @Value("${simulate.ansys.work-path}")
    private String workPath;

    /**
     * ansys参数解析器
     */
    private final AnsysJsonResolver js;

    /**
     * app模块查询接口
     */
    private final AppModuleMapper appModuleMapper;

    /**
     * 任务数据库接口
     */
    private final SubTaskService subTaskService;

    /**
     * 计算结果数据库接口
     */
    private final TaskComputeResultService taskComputeResultService;

    private final ExecutorService readScriptExecutorService;

    private final ExecutorService executeScriptExecutorService;

    /**
     * 计算结果上传接口
     */
    private final ApiProperties apiProperties;

    /**
     * 文件存储接口
     */
    private final MinioUtil minioUtil;

    /**
     * 提交ansys任务
     * @param startComputeVo 任务参数
     */
    @Override
    public void submit(StartComputeVo startComputeVo) {
//        String content = readFileContent(file);
        log.info("=== ansys 仿真主程序发送参数{} ===", JSONUtil.toJsonStr(startComputeVo));
        //参数排序
        Map<String, Object> treeMap = new TreeMap<>(startComputeVo.getParam());
        //参数封装为计算对象
        AnsysSolveVo vo = parseJsonToParam(startComputeVo, JSONUtil.toJsonStr(treeMap));
        vo.getSpaceClaimVo().setTxtPath(modelnamePath + vo.getTaskName() + ".txt");
        startCompute(vo,startComputeVo);
    }

    /**
     * 进入计算
     * @param vo ansys计算参数
     * @param startComputeVo 任务参数
     * @return
     */
    private ResponseResult startCompute(AnsysSolveVo vo, StartComputeVo startComputeVo) {
        //拼接spaceclaim前处理
        SpaceClaimBuilder builder = new SpaceClaimBuilder(new StringBuilder());
        builder.generateToFile(vo.getSpaceClaimVo(), spaceclaimPath + vo.getTaskName() + "-sc.py");

        //SpaceClaim启动指令
        String command1 = spaceclaimPath.charAt(0) + ": && " +
                "cd " + spaceclaimPath + " && " +
                "SpaceClaim.exe /RunScript=" + vo.getTaskName() + "-sc.py /Headless=True /Splash=False /Welcome=False /ExitAfterScript=True";

        ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe", "/c", command1);

        //Ansys启动指令
        String command2 = workPath.charAt(0) + ": && " +
                "cd " + workPath + " && " +
                "\"" + ansysPath + "\" 3d -meshing -tm" + vo.getProcess() + " -i \"" + ansysPyPath + vo.getTaskName() + ".py\"\n";

        ProcessBuilder processBuilder2 = new ProcessBuilder("cmd.exe", "/c", command2);

        String token = LoginUtil.getToken();

        // 子任务未提前创建则先创建，否则关联已创建的子任务
        SubTaskEntity subTaskEntity;

        if (StringUtils.isEmpty(startComputeVo.getSubTaskId())) {
            // 创建子任务
            SubTaskEntity subTask = new SubTaskEntity();
            subTask.setName(startComputeVo.getTaskName() + "-" + IdUtil.simpleUUID());
            subTask.setStatus(ScriptStatusEnum.INIT.getStatus());
            subTask.setUserId(Objects.requireNonNull(LoginUtil.getUserId()).toString());
            subTask.setTaskId(IdUtil.fastSimpleUUID());
            subTask.setAppId(startComputeVo.getAppId());
            subTask.setModuleId(startComputeVo.getAppModuleId());
            subTaskService.save(subTask);

            subTaskEntity = subTask;
        } else {
            subTaskEntity = subTaskService.getBaseMapper().selectById(startComputeVo.getSubTaskId());
        }
        AssertUtil.isNull(subTaskEntity, CodeEnum.SUBTASK_NOT_NULL);

        StringBuilder logContent = new StringBuilder();
        logContent.append(PyUtils.WRAP);

        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                LoginUtil.setToken(token);
                processBuilder.redirectErrorStream(true);

                // 启动进程
                Process process = processBuilder.start();

                Future<?> outputFuture = getOutPutFuture(logContent, process);

                process.waitFor();
                // Wait for the outputFuture to complete
                outputFuture.get();
                determinedSuccess(vo.getSpaceClaimVo().getScdocPath(), vo.getSpaceClaimVo().getTxtPath(), subTaskEntity);

                //更新部件名使前端与ansys中部件名一一对应
                updatePartNames(vo);

                //进入ansys进行计算
                genPythonScript(vo, ansysPyPath + vo.getTaskName() + ".py");

                processBuilder2.redirectErrorStream(true);

                Process process2 = processBuilder2.start();

                Future<?> outputFuture2 = getOutPutFuture(logContent, process2);

                process2.waitFor();
                // Wait for the outputFuture to complete
                outputFuture2.get();

                //检测是否运行成功
                determinedSuccess(vo, workPath + "log\\" + vo.getTaskName() + ".log", subTaskEntity);

                //读取导出温度结果，封装为AnsysResultVo
//                AnsysResultVo resultVo = generateResultVo(vo);
            } catch (InterruptedException e) {
                log.error(e.getMessage());
                // 恢复中断状态
                Thread.currentThread().interrupt();
                // 更新任务表状态为“异常”
                subTaskEntity.setStatus(ScriptStatusEnum.EXCEPTION.getStatus());
                subTaskService.saveOrUpdate(subTaskEntity);
            } catch (Exception e) {
                // 更新任务表状态为“异常”
                log.error(e.getMessage());
                subTaskEntity.setStatus(ScriptStatusEnum.EXCEPTION.getStatus());
                subTaskService.saveOrUpdate(subTaskEntity);
            } finally {
                if (subTaskEntity.getLogPath() == null || subTaskEntity.getCmdLog().isEmpty() || subTaskEntity.getCmdLog() == null || subTaskEntity.getLogPath().isEmpty()) {
                    //将控制台打印的信息写入log文件
                    logContent.delete(0, logContent.indexOf("FILENAME: File Storage.XmlSerializers.dll"));
                    toFile(workPath + "log\\" + vo.getTaskName() + ".log", logContent.toString());
                    subTaskEntity.setLogPath(workPath + "log\\\\" + vo.getTaskName() + ".log");
                    subTaskEntity.setCmdLog(logContent.toString());
                    subTaskService.saveOrUpdate(subTaskEntity);
                }
                LoginUtil.removeAll();
            }
        }, executeScriptExecutorService);
        return ResponseResult.success();
    }

    /**
     * 原部件名为前端中传入的部件名，更新部件名为ansys中的自动命名的部件名
     *
     * @param ansysSolveVo
     * @throws FileNotFoundException
     */
    private void updatePartNames(AnsysSolveVo ansysSolveVo) {
        if (!FileUtil.exist(ansysSolveVo.getSpaceClaimVo().getTxtPath())) {
            throw new BusinessException(CodeEnum.FILE_IS_NULL);
        }
        File file = new File(ansysSolveVo.getSpaceClaimVo().getTxtPath());
        //前端传入的部件名-ansys中自动命名的部件名
        Map<String, String> partName = new HashMap<>();
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file));) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                String[] s = line.split(" ");
                partName.put(s[1], s[0].toLowerCase());
                ansysSolveVo.getPartNames().put(s[1], "");
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
        List<PartVo> partVos = ansysSolveVo.getPartVos();

        //标记默认材料属性的partVo并删除
        int index = -1;
        for (int i = 0; i < partVos.size(); i++) {
            PartVo partVo = partVos.get(i);
            if (partVo.getBodyname() == null) {
                index = i;
            }
            if (partName.containsKey(partVo.getBodyname())) {
                ansysSolveVo.getPartNames().put(partVo.getBodyname(), partVo.getType());
                partVo.setBodyname(partName.get(partVo.getBodyname()));
            }
        }
        Map<String, String> ansysPart = ansysSolveVo.getPartNames();
        ansysPart.forEach((name, type) -> {
            if ("".equals(type)) {
                ansysPart.put(name, "solid");
            }
        });
        if (index != -1) partVos.remove(index);
        ansysSolveVo.setCorresponding(partName);
    }

    /**
     * 生成python脚本
     * @param ansysSolveVo ansys计算参数对象
     * @param ansysPyFile ansys脚本文件存储地址
     */
    public void genPythonScript(AnsysSolveVo ansysSolveVo, String ansysPyFile) {
        Builder builder = new Builder(ansysSolveVo.getExportPath());

        builder.start("ansys.fluent.core", "pyansys");

        //网格脚本拼接
        MeshBuilder meshBuilder = new MeshBuilder(builder);
        meshBuilder.build(ansysSolveVo);

        //网格转换至求解器
        builder.meshToSolver();

        //求解器设置
        SolverBuilder solverBuilder = new SolverBuilder(builder);
        solverBuilder.build(ansysSolveVo.getGravityDirection(), ansysSolveVo.getAppModuleId());

        //材料脚本拼接
        MaterialBuilder materialBuilder = new MaterialBuilder(builder);
        materialBuilder.build(ansysSolveVo);

        //边界条件脚本拼接
        BoundaryBuilder boundaryBuilder = new BoundaryBuilder(builder);
        boundaryBuilder.build(ansysSolveVo.getT0());

        //提交求解并导出
        SubmitBuilder submitBuilder = new SubmitBuilder(builder);
        submitBuilder.build(ansysSolveVo, workPath);

        System.out.println(builder.content.toString());
        builder.toFile(ansysPyFile);
    }

    private AnsysSolveVo parseJsonToParam(StartComputeVo computeVo, String content) throws BusinessException {
        String methodName = getMethodNameFromDb(computeVo.getAppModuleId());
        try {
            Method method = js.getClass().getMethod(methodName, StartComputeVo.class, String.class, Integer.class);
            return (AnsysSolveVo) method.invoke(js, computeVo, content, 1);
        } catch (InvocationTargetException e) {
            Throwable cause = e.getTargetException();
            if (cause instanceof BusinessException) {
                throw (BusinessException) cause;
            }
            log.error(cause.getMessage(), cause);
            throw new BusinessException(CodeEnum.METHOD_INVOCATION_FAILED);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(e.getMessage());
        }
    }

    private String getMethodNameFromDb(String appModuleId) {
        return appModuleMapper.getMethodNameFromDB(appModuleId);
    }

    private Future<?> getOutPutFuture(StringBuilder logContent, Process process) {
        // 读取cmd日志信息
        return readScriptExecutorService.submit(() -> {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info(line);
                    logContent.append(line).append(System.lineSeparator());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    private void determinedSuccess(String scmodelPath, String txtPath, SubTaskEntity subTaskEntity) {
        if (FileUtil.exist(scmodelPath) && FileUtil.exist(txtPath) && !FileUtil.isEmpty(new File(txtPath))) {
            log.info("SpaceClaim前处理已运行结束,接下来进入ansys计算");

            subTaskEntity.setStatus(ScriptStatusEnum.RUNNING.getStatus());
            subTaskService.updateById(subTaskEntity);
        } else {
            subTaskEntity.setStatus(ScriptStatusEnum.EXCEPTION.getStatus());
            subTaskService.updateById(subTaskEntity);
            throw new BusinessException(CodeEnum.SPACECLAIM_ERROR);
        }
    }

    private void determinedSuccess(AnsysSolveVo vo, String logPath, SubTaskEntity subTaskEntity) {
        //计算过程中产生了log文件且文件中包括FAIL表明计算失败
        if (FileUtil.exist(logPath) && "FAIL".equals(TaskMonitor.readLastLine(logPath))) {
            log.info("ansys计算失败");

            subTaskEntity.setStatus(ScriptStatusEnum.EXCEPTION.getStatus());
            subTaskService.updateById(subTaskEntity);
            throw new BusinessException(CodeEnum.ANSYS_ERROR);
        }
        //否则计算完成
        log.info("ansys计算完成");
        // 上传结果文件(新增)
        String resultFilePath = workPath + vo.getTaskName() + ".dat.gz";
        ResponseEntity<String> upload = minioUtil.upload(vo.getTaskName() + ".dat.gz", apiProperties.getModuleName(), resultFilePath);
        if (upload.getStatusCode() != HttpStatus.OK) {
            throw new BusinessException(CodeEnum.TASK_FILE_UPLOAD_FAILED);
        }
//        String url = uploadResultFile(resultFilePath);
        String url = upload.getBody();
//        String url = UploadDownloadFile.uploadResultFile(resultFilePath);

        // 上传结果图片
        String picFilePath = workPath + "PNG\\\\" + vo.getTaskName() + ".png";
        ResponseEntity<String> uploadPic = minioUtil.upload(vo.getTaskName() + ".png", apiProperties.getModuleName(), picFilePath);
        if (uploadPic.getStatusCode() != HttpStatus.OK) {
            throw new BusinessException(CodeEnum.TASK_PIC_UPLOAD_FAILED);
        }
        String body = uploadPic.getBody();

        AnsysResultVo resultVo = generateResultVo(vo);
        String targetPath = workPath + vo.getTaskName() + ".json";
        try (OutputStream outputStream = Files.newOutputStream(Paths.get(targetPath))) {
            outputStream.write(JSONUtil.toJsonStr(resultVo).getBytes());
            outputStream.flush();
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new RuntimeException(e);
        }
        String jsonUrl = upload2FileSystem(targetPath);

        subTaskEntity.setStatus(ScriptStatusEnum.COMPLETE.getStatus());

        TaskComputeResultEntity taskComputeResultEntity = new TaskComputeResultEntity();
        taskComputeResultEntity.setFilePath(workPath + vo.getTaskName() + ".dat.gz");
        taskComputeResultEntity.setPicPath(workPath + "PNG\\\\" + vo.getTaskName() + ".png");
        taskComputeResultEntity.setResultUrl(url);
        taskComputeResultEntity.setPicUrl(body);
        taskComputeResultEntity.setJsonUrl(jsonUrl);

        taskComputeResultService.save(taskComputeResultEntity);

        subTaskEntity.setComputeResultId(taskComputeResultEntity.getId());

        subTaskService.updateById(subTaskEntity);
    }

    private String upload2FileSystem(String path) {
        String filename = Paths.get(path).getFileName().toString();
        ResponseEntity<String> upload = minioUtil.upload(filename, apiProperties.getModuleName(), path);
        if (upload.getStatusCode() != HttpStatus.OK) {
            throw new BusinessException(CodeEnum.TASK_JSON_UPLOAD_FAILED);
        }
        return upload.getBody();
    }

    private String readFileContent(MultipartFile file) throws BusinessException {
        if (file.isEmpty()) {
            throw new BusinessException(CodeEnum.FILE_IS_NULL);
        }

        StringBuilder contentBuilder = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                contentBuilder.append(line).append("\n");
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(CodeEnum.FILE_READ_FAILED);
        }
        return contentBuilder.toString();
    }

    private void toFile(String filename, String str) {
        try (FileWriter fileWriter = new FileWriter(filename);) {
            fileWriter.write(str);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    private AnsysResultVo generateResultVo(AnsysSolveVo vo) throws BusinessException {
        AnsysResultVo resultVo = new AnsysResultVo();
        resultVo.setType(appModuleMapper.getModuleName(vo.getAppModuleId()));
        resultVo.setName(vo.getSpaceClaimVo().getStpName());
        String maxPath = workPath + vo.getTaskName() + "_max.vrp";
        String minPath = workPath + vo.getTaskName() + "_min.vrp";
        Map<String, AnsysTempVo> ansysTempVoMap = new HashMap<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(maxPath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.endsWith("[K]")) {
                    reader.readLine();
                    while ((line = reader.readLine()) != null) {
                        if (line.endsWith("-")) {
                            break;
                        }
                        String[] s = line.replaceAll("\\s+", " ").split(" ");
                        AnsysTempVo tempVo = new AnsysTempVo();
                        tempVo.setComponentName(s[1].contains(vo.getBoard()) ? "BOARD" : s[1]);
                        tempVo.setMaxTemperature(s[2]);
                        ansysTempVoMap.put(s[1], tempVo);
                    }
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(CodeEnum.FILE_READ_FAILED);
        }

        try (BufferedReader reader = new BufferedReader(new FileReader(minPath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.endsWith("[K]")) {
                    reader.readLine();
                    while ((line = reader.readLine()) != null) {
                        if (line.endsWith("-")) {
                            break;
                        }
                        String[] s = line.replaceAll("\\s+", " ").split(" ");
                        AnsysTempVo tempVo = ansysTempVoMap.get(s[1]);
                        tempVo.setMinTemperature(s[2]);
                    }
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(CodeEnum.FILE_READ_FAILED);
        }
        List<AnsysTempVo> tempVos = new ArrayList<>();
        ansysTempVoMap.forEach((k, v) -> {
            tempVos.add(v);
        });
        resultVo.setTemps(tempVos);
        return resultVo;
    }

    /**
     * 上传结果文件
     *
     * @param local
     * @return
     */
    private String uploadResultFile(String local) {
        Map<String, String> params = new HashMap<>(8);
        File file = new File(local);

        Map<String, String> urls = apiProperties.getUrls();
        String url = urls.get("upload-file");
        params.put("moduleName", apiProperties.getModuleName());
        params.put("accessKey", apiProperties.getAccessKey());
        params.put("secretKey", apiProperties.getSecretKey());

        try (Response response = OkHttpUtil.uploadFile(url, file, params);
             ResponseBody responseBody = response.body()) {
            AssertUtil.isNull(responseBody, CodeEnum.UPLOAD_EXCEPTION);
            String body = new String(responseBody.bytes());
            Result<UploadVo> result = JSONUtil.toBean(body, new TypeReference<Result<UploadVo>>() {
            }, false);
            AssertUtil.isFalse(CodeEnum.UPLOAD_EXCEPTION, result.getCode() == 200);
            UploadVo resultData = result.getData();
            return resultData.getUrl();
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        }
    }
}
